package api

import (
	"io/ioutil"
	"moss/conf"
	"moss/meta"
	"moss/status"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
	"github.com/satori/go.uuid"
)

func checkPermission(permission string, ownerAccessKeyId string, currentAccessKeyId string, accessType string) bool {
	return checkACL(permission, ownerAccessKeyId, currentAccessKeyId, accessType)
}

func checkACL(permission string, ownerAccessKeyId string, currentAccessKeyId string, accessType string) bool {
	switch permission {
	case conf.PermissionPublicRead:
		if accessType == conf.AccessTypeWrite {
			return ownerAccessKeyId == currentAccessKeyId
		}
		return true

	case conf.PermissionPublicReadWrite:
		return true

	case conf.PermissionPrivate:
		return ownerAccessKeyId == currentAccessKeyId
	default:
		return ownerAccessKeyId == currentAccessKeyId
	}
	return false
}

func checkBucketACL(permission string, ownerAccessKeyId string, currentAccessKeyId string, accessType string) bool {
	return checkACL(permission, ownerAccessKeyId, currentAccessKeyId, accessType)
}

func CheckBucketACL(permission string, ownerAccessKeyId string, currentAccessKeyId string, accessType string) bool {
	return checkACL(permission, ownerAccessKeyId, currentAccessKeyId, accessType)
}

func checkObjectAclByBucketName(bucketName, objectPermission, ownerAccessKeyId, currentAccessKeyId, accessType string) bool {
	if objectPermission != "" && objectPermission != conf.PermissionObjectDefault {
		return checkACL(objectPermission, ownerAccessKeyId, currentAccessKeyId, accessType)
	} else {
		bucketMeta, ok := meta.GetBucketInfo(bucketName)
		if !ok {
			return false
		}
		return checkACL(bucketMeta.Permission, ownerAccessKeyId, currentAccessKeyId, accessType)
	}
}

func checkObjectAclByBucketMeta(bucketPermission, objectPermission, ownerAccessKeyId, currentAccessKeyId, accessType string) bool {
	if objectPermission != "" && objectPermission != conf.PermissionObjectDefault {
		return checkACL(objectPermission, ownerAccessKeyId, currentAccessKeyId, accessType)
	} else {
		return checkACL(bucketPermission, ownerAccessKeyId, currentAccessKeyId, accessType)
	}
}

func parseTimeStr(timeStr string) (time.Time, bool) {
	timeVal, err := time.Parse(http.TimeFormat, timeStr)
	if err != nil {
		return time.Time{}, false
	}
	return timeVal, true
}

func validDataCenter(dataCenter string) bool {
	dataCenterList := strings.Split(conf.Config.Name.DataCenterList, ",")
	for _, v := range dataCenterList {
		if v == dataCenter {
			return true
		}
	}
	return false
}

func validPermissionForBucket(permission string) bool {
	return permission == conf.PermissionPublicRead ||
		permission == conf.PermissionPublicReadWrite ||
		permission == conf.PermissionPrivate
}

func validPermission(permission string) bool {
	return permission == conf.PermissionPublicRead ||
		permission == conf.PermissionPublicReadWrite ||
		permission == conf.PermissionPrivate ||
		permission == conf.PermissionObjectDefault
}

func validIdentifier(name string) bool {
	if len(name) < 0 || len(name) > conf.Config.Limit.MaxObjectKeyLength {
		return false
	}

	return !strings.ContainsAny(name, "&=,?<> ")
}

func getSizeOfUserMeta(userMeta string) int {
	return len(userMeta)
}

func checkPreHeaders(ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch string, objectMeta *meta.Object) status.Status {
	lastModifiedVal, err := time.Parse(conf.OssTimeFormat, objectMeta.LastModified)
	if err != nil {
		return status.InternalError
	}

	modifiedSinceVal, hasModifiedSince :=  parseTimeStr(ifModifiedSince)
	unmodifiedSinceVal, hasUnmodifiedSince := parseTimeStr(ifUnmodifiedSince)

	if hasUnmodifiedSince && lastModifiedVal.After(unmodifiedSinceVal) {
		return status.PreconditionFailed
	}

	if len(ifMatch) > 0 && ifMatch != objectMeta.ETag {
		return status.PreconditionFailed
	}

	if hasModifiedSince && lastModifiedVal.Before(modifiedSinceVal) {
		return status.NotModified
	}

	if len(ifNoneMatch) > 0 && ifNoneMatch == objectMeta.ETag {
		return status.NotModified
	}

	return status.Success
}

func getHeaders(objectMeta *meta.Object, objectTargetMeta *meta.Object) (map[string]string, status.Status) {
	headers := make(map[string]string)

	userMeta := strings.Split(objectMeta.UserMeta, conf.UserMeaSep)
	if len(userMeta)%2 == 0 {
		for i := 0; i+1 < len(userMeta); i += 2 {
			headers[userMeta[i]] = userMeta[i+1]
		}
	}

	headers[conf.HTTPHeaderOssObjectType] = objectMeta.Type
	headers[conf.HTTPHeaderOssStorageClass] = objectMeta.StorageClass
	headers[conf.HTTPHeaderOssServerSideEncryption] = objectMeta.Encryption
	headers[conf.HTTPHeaderOssCRC64] = objectMeta.Crc64Checksum

	if objectTargetMeta != nil {
		headers[conf.HTTPHeaderContentLength] = strconv.FormatInt(objectTargetMeta.Size, 10)
		headers[conf.HTTPHeaderEtag] = objectTargetMeta.ETag

		objectLastModifiedVal, err := time.Parse(conf.OssTimeFormat, objectMeta.LastModified)
		if err != nil {
			return nil, status.InternalError
		}
		objectTargetLastModifiedVal, err := time.Parse(conf.OssTimeFormat, objectTargetMeta.LastModified)
		if err != nil {
			return nil, status.InternalError
		}
		if objectLastModifiedVal.After(objectTargetLastModifiedVal) {
			headers[conf.HTTPHeaderLastModified] = objectMeta.LastModified
		} else {
			headers[conf.HTTPHeaderLastModified] = objectTargetMeta.LastModified
		}
	} else {
		headers[conf.HTTPHeaderContentLength] = strconv.FormatInt(objectMeta.Size, 10)
		headers[conf.HTTPHeaderEtag] = objectMeta.ETag
		headers[conf.HTTPHeaderLastModified] = objectMeta.LastModified
	}

	if objectMeta.Type == conf.ObjectTypeAppendable {
		headers[conf.HTTPHeaderOssNextAppendPosition] = strconv.FormatInt(objectMeta.Size, 10)
	}

	return headers, status.Success
}

func removeAll(objectLocation string) error {
	err := os.RemoveAll(objectLocation)
	if err != nil {
		return err
	}
	if strings.Contains(objectLocation, "/") {
		names := strings.Split(objectLocation, "/")
		n := len(names) - 1
		dir := strings.TrimSuffix(objectLocation, "/"+names[n])
		for i := 1; i < n; i++ {
			fileInfo, err := ioutil.ReadDir(dir)
			if err != nil {
				return err
			}
			if len(fileInfo) == 0 {
				err = os.RemoveAll(dir)
				if err != nil {
					return err
				}
			} else {
				return nil
			}
			dir = strings.TrimSuffix(dir, "/"+names[n-i])
		}
	}
	return nil
}

func buildUploadId() string {
	u, _ := uuid.NewV4()
	return strings.Replace(u.String(), "-", "", 4)
}
